"""
IR Normalizer - Transforms and normalizes IR for C++ code generation.

This module provides normalization transformations that prepare IR for C++ code generation.
Key transformations include:
- Auto-property expansion to getter/setter methods
- Access modifier normalization
- Type mapping preparation
"""

from copy import deepcopy

from orchestrator.ir.nodes import (
    ClassDeclaration,
    FieldDeclaration,
    MethodDeclaration,
    Parameter,
    PropertyDeclaration,
)


class IRNormalizer:
    """
    Normalizes IR for C++ code generation.

    This class applies various transformations to make the IR more suitable
    for C++ code generation, such as expanding properties to methods.
    """

    def __init__(self) -> None:
        """Initialize the normalizer"""
        self.transformations_applied: list[str] = []

    def normalize(self, ir_class: ClassDeclaration) -> ClassDeclaration:
        """
        Apply all normalization transformations to a class.

        Args:
            ir_class: The class to normalize

        Returns:
            Normalized class (new instance, original is unchanged)
        """
        # Create a deep copy to avoid modifying the original
        normalized = deepcopy(ir_class)
        self.transformations_applied = []

        # Apply transformations in order
        normalized = self._expand_auto_properties(normalized)
        normalized = self._normalize_modifiers(normalized)
        normalized = self._add_backing_fields_for_properties(normalized)

        return normalized

    def _expand_auto_properties(self, ir_class: ClassDeclaration) -> ClassDeclaration:
        """
        Expand C# auto-properties to getter/setter method pattern.

        In C#:
            public string Name { get; set; }

        Becomes in IR:
            - private field: _name
            - public getter: string GetName()
            - public setter: void SetName(string value)
        """
        new_methods: list[MethodDeclaration] = []

        for prop in ir_class.properties:
            if not prop.is_auto_property:
                continue

            # Generate getter method
            if prop.has_getter:
                getter = self._create_getter_method(prop)
                new_methods.append(getter)
                self.transformations_applied.append(
                    f"Property->Getter: {prop.name} -> Get{prop.name}"
                )

            # Generate setter method
            if prop.has_setter:
                setter = self._create_setter_method(prop)
                new_methods.append(setter)
                self.transformations_applied.append(
                    f"Property->Setter: {prop.name} -> Set{prop.name}"
                )

        # Add new methods to the class
        ir_class.methods.extend(new_methods)
        return ir_class

    def _create_getter_method(self, prop: PropertyDeclaration) -> MethodDeclaration:
        """Create a getter method from a property"""
        # Convert property modifiers to method modifiers
        method_modifiers = [m for m in prop.modifiers if m in ["public", "protected", "private"]]
        if not method_modifiers:
            method_modifiers = ["public"]

        # Add const for C++ (getters should be const)
        if "const" not in method_modifiers:
            method_modifiers.append("const")

        return MethodDeclaration(
            name=f"Get{prop.name}",
            return_type=prop.property_type,
            parameters=[],
            modifiers=method_modifiers,
            metadata={
                "generated_from": "property_getter",
                "original_property": prop.name,
            },
        )

    def _create_setter_method(self, prop: PropertyDeclaration) -> MethodDeclaration:
        """Create a setter method from a property"""
        # Convert property modifiers to method modifiers
        method_modifiers = [m for m in prop.modifiers if m in ["public", "protected", "private"]]
        if not method_modifiers:
            method_modifiers = ["public"]

        return MethodDeclaration(
            name=f"Set{prop.name}",
            return_type="void",
            parameters=[Parameter(name="value", param_type=prop.property_type)],
            modifiers=method_modifiers,
            metadata={
                "generated_from": "property_setter",
                "original_property": prop.name,
            },
        )

    def _add_backing_fields_for_properties(self, ir_class: ClassDeclaration) -> ClassDeclaration:
        """
        Add private backing fields for auto-properties.

        C# auto-properties have implicit backing fields. We need to make these
        explicit for C++ code generation.
        """
        new_fields: list[FieldDeclaration] = []

        for prop in ir_class.properties:
            if not prop.is_auto_property:
                continue

            # Create backing field with underscore prefix (C++ convention)
            backing_field = FieldDeclaration(
                name=f"_{prop.name[0].lower()}{prop.name[1:]}",  # _camelCase
                field_type=prop.property_type,
                modifiers=["private"],
                initializer=prop.initializer,
                metadata={
                    "generated_from": "property_backing_field",
                    "original_property": prop.name,
                },
            )
            new_fields.append(backing_field)
            self.transformations_applied.append(
                f"BackingField: {prop.name} -> {backing_field.name}"
            )

        # Add new fields to the class
        ir_class.fields.extend(new_fields)
        return ir_class

    def _normalize_modifiers(self, ir_class: ClassDeclaration) -> ClassDeclaration:
        """
        Normalize access modifiers for C++.

        C# has: public, private, protected, internal, protected internal
        C++ has: public, private, protected

        This transformation maps C# modifiers to C++ equivalents.
        """
        # Normalize class modifiers
        ir_class.modifiers = self._map_modifiers_to_cpp(ir_class.modifiers)

        # Normalize member modifiers
        for field in ir_class.fields:
            field.modifiers = self._map_modifiers_to_cpp(field.modifiers)

        for prop in ir_class.properties:
            prop.modifiers = self._map_modifiers_to_cpp(prop.modifiers)

        for method in ir_class.methods:
            method.modifiers = self._map_modifiers_to_cpp(method.modifiers)

        for ctor in ir_class.constructors:
            ctor.modifiers = self._map_modifiers_to_cpp(ctor.modifiers)

        self.transformations_applied.append("Normalized modifiers for C++")
        return ir_class

    def _map_modifiers_to_cpp(self, modifiers: list[str]) -> list[str]:
        """
        Map C# modifiers to C++ equivalents.

        Mappings:
        - internal -> public (C++ has no internal concept)
        - protected internal -> protected
        - sealed -> final (in C++11+)
        """
        cpp_modifiers: list[str] = []

        for modifier in modifiers:
            if modifier in ["public", "private", "protected"]:
                cpp_modifiers.append(modifier)
            elif modifier == "internal":
                # Map internal to public (C++ has no assembly/module concept)
                if "public" not in cpp_modifiers:
                    cpp_modifiers.append("public")
            elif modifier == "sealed":
                # Map sealed to final (C++11)
                cpp_modifiers.append("final")
            elif modifier in ["static", "virtual", "abstract", "override", "const", "readonly"]:
                # Keep these as-is (though they'll be handled differently in codegen)
                cpp_modifiers.append(modifier)

        return cpp_modifiers


def normalize_ir(ir_class: ClassDeclaration) -> ClassDeclaration:
    """
    Convenience function to normalize a class IR.

    Args:
        ir_class: The class to normalize

    Returns:
        Normalized class (new instance)
    """
    normalizer = IRNormalizer()
    return normalizer.normalize(ir_class)
